"""
This module represents the Marketplace.

Computer Systems Architecture Course
Assignment 1
March 2020
"""

from threading import Lock

class Marketplace:
    """
    Class that represents the Marketplace. It's the central part of the implementation.
    The producers and consumers use its methods concurrently.
    """
    debug = False

    def __init__(self, queue_size_per_producer):
        """
        Constructor

        :type queue_size_per_producer: Int
        :param queue_size_per_producer: the maximum size of a queue associated with each producer
        """
        self.queue_size_per_producer = queue_size_per_producer

        self.producer_lock = Lock()
        self.consumer_lock = Lock()

        self.producers_list = []
        self.available_products = []

        self.consumers_cart = []

    def register_producer(self):
        """
        Returns an id for the producer that calls this.
        """
        self.producer_lock.acquire()

        producer_id = len(self.producers_list)
        self.producers_list.append([])

        self.producer_lock.release()

        if self.debug:
            print("[REG_PROD] NEW")
            print("[REG_PROD] Producer " + str(producer_id) + " has registered.")

        return producer_id

    def publish(self, producer_id, product):
        """
        Adds the product provided by the producer to the marketplace

        :type producer_id: String
        :param producer_id: producer id

        :type product: Product
        :param product: the Product that will be published in the Marketplace

        :returns True or False. If the caller receives False, it should wait and then try again.
        """
        p_id = int(producer_id)

        if p_id >= len(self.producers_list):
            return False

        producer_list = self.producers_list[p_id]

        if self.queue_size_per_producer <= len(producer_list):
            return False

        producer_list.append(product)

        self.available_products.append(product)

        if self.debug:
            print("[PUBLISH] NEW")
            print("[PUBLISH] Producer " + str(producer_id) + " inserted element: " + str(product))
            print("[PUBLISH] Producer list: " + str(producer_list))
            print("[PUBLISH] Available products now: " + str(self.available_products))

        return True

    def new_cart(self):
        """
        Creates a new cart for the consumer

        :returns an int representing the cart_id
        """
        self.consumer_lock.acquire()

        cart_id = len(self.consumers_cart)
        self.consumers_cart.append([])

        self.consumer_lock.release()


        if self.debug:
            print("[NEW_CART] NEW")
            print("[NEW_CART] Cart " + str(cart_id) + " created.")

        return cart_id

    def add_to_cart(self, cart_id, product):
        """
        Adds a product to the given cart. The method returns

        :type cart_id: Int
        :param cart_id: id cart

        :type product: Product
        :param product: the product to add to cart

        :returns True or False. If the caller receives False, it should wait and then try again
        """
        if cart_id >= len(self.consumers_cart) or product not in self.available_products:
            return False

        self.consumer_lock.acquire()

        if product in self.available_products:
            self.available_products.remove(product)

        self.consumer_lock.release()

        self.consumers_cart[cart_id].append(product)

        if self.debug:
            print("[ADD_TO_CART] NEW")
            print("[ADD_TO_CART] Cart " + str(cart_id) + " has new element: " + str(product))
            print("[ADD_TO_CART] Cart contents: " + str(self.consumers_cart[cart_id]))
            print("[ADD_TO_CART] Available products now: " + str(self.available_products))

        return True

    def remove_from_cart(self, cart_id, product):
        """
        Removes a product from cart.

        :type cart_id: Int
        :param cart_id: id cart

        :type product: Product
        :param product: the product to remove from cart
        """
        if cart_id >= len(self.consumers_cart):
            return False

        cart = self.consumers_cart[cart_id]

        if product not in cart:
            return False

        self.available_products.append(product)
        cart.remove(product)

        if self.debug:
            print("[REMOVE_FROM_CART] NEW")
            print("[REMOVE_FROM_CART] Cart " + str(cart_id) + " removed element: " + str(product))
            print("[REMOVE_FROM_CART] Cart contents: " + str(cart))
            print("[REMOVE_FROM_CART] Available products now: " + str(self.available_products))

        return False

    def place_order(self, cart_id):
        """
        Return a list with all the products in the cart.

        :type cart_id: Int
        :param cart_id: id cart
        """
        if cart_id >= len(self.consumers_cart):
            return None

        cart = self.consumers_cart[cart_id]

        for product in cart:
            found_producer = False

            self.producer_lock.acquire()

            for producer_list in self.producers_list:
                if not found_producer:
                    for producer_product in producer_list:
                        if producer_product == product:
                            producer_list.remove(product)
                            found_producer = True
                else:
                    if self.debug:
                        print("[PLACE_ORDER] NEW")
                        print("[PLACE_ORDER] Cart: " + str(cart))
                        print("[PLACE_ORDER] Producer " + " has now list: " + str(producer_list))
                    break

            self.producer_lock.release()

        return cart
